﻿using Avalonia.Collections;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Mesen.Utilities
{
	//Copy of Avalonia's AvaloniaList with an added Replace function to avoid triggering 2 updates
	public partial class MesenList<T>
	{
		public void Replace(IList<T> items)
		{
			_inner.Clear();
			_inner.Capacity = items.Count;
			_inner.AddRange(items);
			_collectionChanged?.Invoke(this, EventArgsCache.ResetCollectionChanged);
		}

		public List<T> GetInnerList()
		{
			return _inner;
		}
	}

	/// <summary>
	/// A notifying list.
	/// </summary>
	/// <typeparam name="T">The type of the list items.</typeparam>
	/// <remarks>
	/// <para>
	/// AvaloniaList is similar to <see cref="System.Collections.ObjectModel.ObservableCollection{T}"/>
	/// with a few added features:
	/// </para>
	/// 
	/// <list type="bullet">
	/// <item>
	/// It can be configured to notify the <see cref="CollectionChanged"/> event with a
	/// <see cref="NotifyCollectionChangedAction.Remove"/> action instead of a
	/// <see cref="NotifyCollectionChangedAction.Reset"/> when the list is cleared by
	/// setting <see cref="ResetBehavior"/> to <see cref="ResetBehavior.Remove"/>.
	/// removed
	/// </item>
	/// <item>
	/// A <see cref="Validate"/> function can be used to validate each item before insertion.
	/// removed
	/// </item>
	/// </list>
	/// </remarks>
	public partial class MesenList<T> : IAvaloniaList<T>, IList
	{
		private readonly List<T> _inner;
		private NotifyCollectionChangedEventHandler? _collectionChanged;

		/// <summary>
		/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
		/// </summary>
		public MesenList()
		{
			_inner = new List<T>();
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AvaloniaList{T}"/>.
		/// </summary>
		/// <param name="capacity">Initial list capacity.</param>
		public MesenList(int capacity)
		{
			_inner = new List<T>(capacity);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
		/// </summary>
		/// <param name="items">The initial items for the collection.</param>
		public MesenList(IEnumerable<T> items)
		{
			_inner = new List<T>(items);
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="AvaloniaList{T}"/> class.
		/// </summary>
		/// <param name="items">The initial items for the collection.</param>
		public MesenList(params T[] items)
		{
			_inner = new List<T>(items);
		}

		/// <summary>
		/// Raised when a change is made to the collection's items.
		/// </summary>
		public event NotifyCollectionChangedEventHandler? CollectionChanged
		{
			add => _collectionChanged += value;
			remove => _collectionChanged -= value;
		}

		/// <summary>
		/// Raised when a property on the collection changes.
		/// </summary>
		public event PropertyChangedEventHandler? PropertyChanged;

		/// <summary>
		/// Gets the number of items in the collection.
		/// </summary>
		public int Count => _inner.Count;

		/// <summary>
		/// Gets or sets the reset behavior of the list.
		/// </summary>
		public ResetBehavior ResetBehavior { get; set; }

		/// <summary>
		/// Gets or sets a validation routine that can be used to validate items before they are
		/// added.
		/// </summary>
		public Action<T>? Validate { get; set; }

		/// <inheritdoc/>
		bool IList.IsFixedSize => false;

		/// <inheritdoc/>
		bool IList.IsReadOnly => false;

		/// <inheritdoc/>
		int ICollection.Count => _inner.Count;

		/// <inheritdoc/>
		bool ICollection.IsSynchronized => false;

		/// <inheritdoc/>
		object ICollection.SyncRoot => this;

		/// <inheritdoc/>
		bool ICollection<T>.IsReadOnly => false;

		/// <summary>
		/// Gets or sets the item at the specified index.
		/// </summary>
		/// <param name="index">The index.</param>
		/// <returns>The item.</returns>
		public T this[int index]
		{
			get
			{
				return _inner[index];
			}

			set
			{
				Validate?.Invoke(value);

				T old = _inner[index];

				if(!EqualityComparer<T>.Default.Equals(old, value)) {
					_inner[index] = value;

					if(_collectionChanged != null) {
						var e = new NotifyCollectionChangedEventArgs(
							 NotifyCollectionChangedAction.Replace,
							 value,
							 old,
							 index);
						_collectionChanged(this, e);
					}
				}
			}
		}

		/// <summary>
		/// Gets or sets the item at the specified index.
		/// </summary>
		/// <param name="index">The index.</param>
		/// <returns>The item.</returns>
		object? IList.this[int index]
		{
			get { return this[index]; }
			set { this[index] = (T)value!; }
		}

		/// <summary>
		/// Gets or sets the total number of elements the internal data structure can hold without resizing.
		/// </summary>
		public int Capacity
		{
			get => _inner.Capacity;
			set => _inner.Capacity = value;
		}

		/// <summary>
		/// Adds an item to the collection.
		/// </summary>
		/// <param name="item">The item.</param>
		public virtual void Add(T item)
		{
			Validate?.Invoke(item);
			int index = _inner.Count;
			_inner.Add(item);
			NotifyAdd(item, index);
		}

		/// <summary>
		/// Adds multiple items to the collection.
		/// </summary>
		/// <param name="items">The items.</param>
		public virtual void AddRange(IEnumerable<T> items) => InsertRange(_inner.Count, items);

		/// <summary>
		/// Removes all items from the collection.
		/// </summary>
		public virtual void Clear()
		{
			if(Count > 0) {
				if(_collectionChanged != null) {
					var e = ResetBehavior == ResetBehavior.Reset ?
						 EventArgsCache.ResetCollectionChanged :
						 new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, _inner.ToArray(), 0);

					_inner.Clear();

					_collectionChanged(this, e);
				} else {
					_inner.Clear();
				}

				NotifyCountChanged();
			}
		}

		/// <summary>
		/// Tests if the collection contains the specified item.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>True if the collection contains the item; otherwise false.</returns>
		public bool Contains(T item)
		{
			return _inner.Contains(item);
		}

		/// <summary>
		/// Copies the collection's contents to an array.
		/// </summary>
		/// <param name="array">The array.</param>
		/// <param name="arrayIndex">The first index of the array to copy to.</param>
		public void CopyTo(T[] array, int arrayIndex)
		{
			_inner.CopyTo(array, arrayIndex);
		}

		/// <summary>
		/// Returns an enumerator that enumerates the items in the collection.
		/// </summary>
		/// <returns>An <see cref="IEnumerator{T}"/>.</returns>
		IEnumerator<T> IEnumerable<T>.GetEnumerator()
		{
			return new Enumerator(_inner);
		}

		/// <inheritdoc/>
		IEnumerator IEnumerable.GetEnumerator()
		{
			return new Enumerator(_inner);
		}

		public Enumerator GetEnumerator()
		{
			return new Enumerator(_inner);
		}

		/// <summary>
		/// Gets a range of items from the collection.
		/// </summary>
		/// <param name="index">The zero-based <see cref="AvaloniaList{T}"/> index at which the range starts.</param>
		/// <param name="count">The number of elements in the range.</param>
		public IEnumerable<T> GetRange(int index, int count)
		{
			return _inner.GetRange(index, count);
		}

		/// <summary>
		/// Gets the index of the specified item in the collection.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>
		/// The index of the item or -1 if the item is not contained in the collection.
		/// </returns>
		public int IndexOf(T item)
		{
			return _inner.IndexOf(item);
		}

		/// <summary>
		/// Inserts an item at the specified index.
		/// </summary>
		/// <param name="index">The index.</param>
		/// <param name="item">The item.</param>
		public virtual void Insert(int index, T item)
		{
			Validate?.Invoke(item);
			_inner.Insert(index, item);
			NotifyAdd(item, index);
		}

		/// <summary>
		/// Inserts multiple items at the specified index.
		/// </summary>
		/// <param name="index">The index.</param>
		/// <param name="items">The items.</param>
		public virtual void InsertRange(int index, IEnumerable<T> items)
		{
			_ = items ?? throw new ArgumentNullException(nameof(items));

			bool willRaiseCollectionChanged = _collectionChanged != null;
			bool hasValidation = Validate != null;

			if(items is IList list) {
				if(list.Count > 0) {
					if(list is ICollection<T> collection) {
						if(hasValidation) {
							foreach(T item in collection) {
								Validate!(item);
							}
						}

						_inner.InsertRange(index, collection);
						NotifyAdd(list, index);
					} else {
						EnsureCapacity(_inner.Count + list.Count);

						using(IEnumerator<T> en = items.GetEnumerator()) {
							int insertIndex = index;

							while(en.MoveNext()) {
								T item = en.Current;

								if(hasValidation) {
									Validate!(item);
								}

								_inner.Insert(insertIndex++, item);
							}
						}

						NotifyAdd(list, index);
					}
				}
			} else {
				using(IEnumerator<T> en = items.GetEnumerator()) {
					if(en.MoveNext()) {
						// Avoid allocating list for collection notification if there is no event subscriptions.
						List<T>? notificationItems = willRaiseCollectionChanged ?
							 new List<T>() :
							 null;

						int insertIndex = index;

						do {
							T item = en.Current;

							if(hasValidation) {
								Validate!(item);
							}

							_inner.Insert(insertIndex++, item);

							notificationItems?.Add(item);

						} while(en.MoveNext());

						if(notificationItems is not null) {
							NotifyAdd(notificationItems, index);
						} else {
							NotifyCountChanged();
						}
					}
				}
			}
		}

		/// <summary>
		/// Moves an item to a new index.
		/// </summary>
		/// <param name="oldIndex">The index of the item to move.</param>
		/// <param name="newIndex">The index to move the item to.</param>
		public void Move(int oldIndex, int newIndex)
		{
			var item = this[oldIndex];
			_inner.RemoveAt(oldIndex);
			_inner.Insert(newIndex, item);

			if(_collectionChanged != null) {
				var e = new NotifyCollectionChangedEventArgs(
					 NotifyCollectionChangedAction.Move,
					 item,
					 newIndex,
					 oldIndex);
				_collectionChanged(this, e);
			}
		}

		/// <summary>
		/// Moves multiple items to a new index.
		/// </summary>
		/// <param name="oldIndex">The first index of the items to move.</param>
		/// <param name="count">The number of items to move.</param>
		/// <param name="newIndex">The index to move the items to.</param>
		public void MoveRange(int oldIndex, int count, int newIndex)
		{
			var items = _inner.GetRange(oldIndex, count);
			var modifiedNewIndex = newIndex;
			_inner.RemoveRange(oldIndex, count);

			if(newIndex > oldIndex) {
				modifiedNewIndex -= count;
			}

			_inner.InsertRange(modifiedNewIndex, items);

			if(_collectionChanged != null) {
				var e = new NotifyCollectionChangedEventArgs(
					 NotifyCollectionChangedAction.Move,
					 items,
					 newIndex,
					 oldIndex);
				_collectionChanged(this, e);
			}
		}

		/// <summary>
		/// Ensures that the capacity of the list is at least <see cref="Capacity"/>.
		/// </summary>
		/// <param name="capacity">The capacity.</param>
		public void EnsureCapacity(int capacity)
		{
			// Adapted from List<T> implementation.
			var currentCapacity = _inner.Capacity;

			if(currentCapacity < capacity) {
				var newCapacity = currentCapacity == 0 ? 4 : currentCapacity * 2;

				if(newCapacity < capacity) {
					newCapacity = capacity;
				}

				_inner.Capacity = newCapacity;
			}
		}

		/// <summary>
		/// Removes an item from the collection.
		/// </summary>
		/// <param name="item">The item.</param>
		/// <returns>True if the item was found and removed, otherwise false.</returns>
		public virtual bool Remove(T item)
		{
			int index = _inner.IndexOf(item);

			if(index != -1) {
				_inner.RemoveAt(index);
				NotifyRemove(item, index);
				return true;
			}

			return false;
		}

		/// <summary>
		/// Removes multiple items from the collection.
		/// </summary>
		/// <param name="items">The items.</param>
		public virtual void RemoveAll(IEnumerable<T> items)
		{
			_ = items ?? throw new ArgumentNullException(nameof(items));

			var hItems = new HashSet<T>(items);

			int counter = 0;
			for(int i = _inner.Count - 1; i >= 0; --i) {
				if(hItems.Contains(_inner[i])) {
					counter += 1;
				} else if(counter > 0) {
					RemoveRange(i + 1, counter);
					counter = 0;
				}
			}

			if(counter > 0)
				RemoveRange(0, counter);
		}

		/// <summary>
		/// Removes the item at the specified index.
		/// </summary>
		/// <param name="index">The index.</param>
		public virtual void RemoveAt(int index)
		{
			T item = _inner[index];
			_inner.RemoveAt(index);
			NotifyRemove(item, index);
		}

		/// <summary>
		/// Removes a range of elements from the collection.
		/// </summary>
		/// <param name="index">The first index to remove.</param>
		/// <param name="count">The number of items to remove.</param>
		public virtual void RemoveRange(int index, int count)
		{
			if(count > 0) {
				var list = _inner.GetRange(index, count);
				_inner.RemoveRange(index, count);
				NotifyRemove(list, index);
			}
		}

		/// <inheritdoc/>
		int IList.Add(object? value)
		{
			int index = Count;
			Add((T)value!);
			return index;
		}

		/// <inheritdoc/>
		bool IList.Contains(object? value)
		{
			return Contains((T)value!);
		}

		/// <inheritdoc/>
		void IList.Clear()
		{
			Clear();
		}

		/// <inheritdoc/>
		int IList.IndexOf(object? value)
		{
			return IndexOf((T)value!);
		}

		/// <inheritdoc/>
		void IList.Insert(int index, object? value)
		{
			Insert(index, (T)value!);
		}

		/// <inheritdoc/>
		void IList.Remove(object? value)
		{
			Remove((T)value!);
		}

		/// <inheritdoc/>
		void IList.RemoveAt(int index)
		{
			RemoveAt(index);
		}

		/// <inheritdoc/>
		void ICollection.CopyTo(Array array, int index)
		{
			if(array == null) {
				throw new ArgumentNullException(nameof(array));
			}

			if(array.Rank != 1) {
				throw new ArgumentException("Multi-dimensional arrays are not supported.");
			}

			if(array.GetLowerBound(0) != 0) {
				throw new ArgumentException("Non-zero lower bounds are not supported.");
			}

			if(index < 0) {
				throw new ArgumentException("Invalid index.");
			}

			if(array.Length - index < Count) {
				throw new ArgumentException("The target array is too small.");
			}

			if(array is T[] tArray) {
				_inner.CopyTo(tArray, index);
			} else {
				//
				// Catch the obvious case assignment will fail.
				// We can't find all possible problems by doing the check though.
				// For example, if the element type of the Array is derived from T,
				// we can't figure out if we can successfully copy the element beforehand.
				//
				Type targetType = array.GetType().GetElementType()!;
				Type sourceType = typeof(T);
				if(!(targetType.IsAssignableFrom(sourceType) || sourceType.IsAssignableFrom(targetType))) {
					throw new ArgumentException("Invalid array type");
				}

				//
				// We can't cast array of value type to object[], so we don't support
				// widening of primitive types here.
				//
				if(array is not object?[] objects) {
					throw new ArgumentException("Invalid array type");
				}

				int count = _inner.Count;
				try {
					for(int i = 0; i < count; i++) {
						objects[index++] = _inner[i];
					}
				} catch(ArrayTypeMismatchException) {
					throw new ArgumentException("Invalid array type");
				}
			}
		}

		/// <summary>
		/// Raises the <see cref="CollectionChanged"/> event with an add action.
		/// </summary>
		/// <param name="t">The items that were added.</param>
		/// <param name="index">The starting index.</param>
		private void NotifyAdd(IList t, int index)
		{
			if(_collectionChanged != null) {
				var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, t, index);
				_collectionChanged(this, e);
			}

			NotifyCountChanged();
		}

		/// <summary>
		/// Raises the <see cref="CollectionChanged"/> event with a add action.
		/// </summary>
		/// <param name="item">The item that was added.</param>
		/// <param name="index">The starting index.</param>
		private void NotifyAdd(T item, int index)
		{
			if(_collectionChanged != null) {
				var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new[] { item }, index);
				_collectionChanged(this, e);
			}

			NotifyCountChanged();
		}

		/// <summary>
		/// Raises the <see cref="PropertyChanged"/> event when the <see cref="Count"/> property
		/// changes.
		/// </summary>
		private void NotifyCountChanged()
		{
			PropertyChanged?.Invoke(this, EventArgsCache.CountPropertyChanged);
		}

		/// <summary>
		/// Raises the <see cref="CollectionChanged"/> event with a remove action.
		/// </summary>
		/// <param name="t">The items that were removed.</param>
		/// <param name="index">The starting index.</param>
		private void NotifyRemove(IList t, int index)
		{
			if(_collectionChanged != null) {
				var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, t, index);
				_collectionChanged(this, e);
			}

			NotifyCountChanged();
		}

		/// <summary>
		/// Raises the <see cref="CollectionChanged"/> event with a remove action.
		/// </summary>
		/// <param name="item">The item that was removed.</param>
		/// <param name="index">The starting index.</param>
		private void NotifyRemove(T item, int index)
		{
			if(_collectionChanged != null) {
				var e = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, new[] { item }, index);
				_collectionChanged(this, e);
			}

			NotifyCountChanged();
		}

		/// <summary>
		/// Enumerates the elements of a <see cref="AvaloniaList{T}"/>.
		/// </summary>
		public struct Enumerator : IEnumerator<T>
		{
			private List<T>.Enumerator _innerEnumerator;

			public Enumerator(List<T> inner)
			{
				_innerEnumerator = inner.GetEnumerator();
			}

			public bool MoveNext()
			{
				return _innerEnumerator.MoveNext();
			}

			void IEnumerator.Reset()
			{
				((IEnumerator)_innerEnumerator).Reset();
			}

			public T Current => _innerEnumerator.Current;

			object? IEnumerator.Current => Current;

			public void Dispose()
			{
				_innerEnumerator.Dispose();
			}
		}
	}

	internal static class EventArgsCache
	{
		internal static readonly PropertyChangedEventArgs CountPropertyChanged = new PropertyChangedEventArgs(nameof(AvaloniaList<object>.Count));
		internal static readonly NotifyCollectionChangedEventArgs ResetCollectionChanged = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
	}
}
